#!/usr/bin/perl

# Generates a verilog instantiation of an x-by-y network

use strict;
use warnings;
use Getopt::Long;



my %args = (
	"x" => 1,
	"y" => 1,
	"cd" => 0,
	"o" => "../final/network.v",
	"ovr" => 0,
);

GetOptions( \%args,
	"x=i",			# routers in X-dimension
	"y=i",			# routers in Y-dimension
	"cd=i",			# channel delay, can be 0
	"o=s",			# output file
	"ovr",
);

my $comment = "// A $args{x}x$args{y} mesh network";
$args{x}--; $args{y}--; # decrement to start from 0



# create routers by hooking up their inputs and outputs to nets.
my %routers = ();
for my $x (0 .. $args{x}) {
	for my $y (0 .. $args{y}) {
		$routers{"ROUTER_${x}_$y"} = {
			"id" => {
				"x" => $x,
				"y" => $y,		
			},
			"in" => {
				"clock" => "CLK", "reset" => "RESET",
				
				"data_to_queue_e" => "",
				"data_to_queue_s" => "",
				"data_to_queue_w" => "",
				"data_to_queue_n" => "",
				"data_to_queue_h" => "",
				 
				"valid_to_queue_e" => "",
				"valid_to_queue_s" => "",
				"valid_to_queue_w" => "",
				"valid_to_queue_n" => "",
				"valid_to_queue_h" => "",
				
				"credits_to_om_e" => "",
				"credits_to_om_s" => "",
				"credits_to_om_w" => "",
				"credits_to_om_n" => "",
				"credits_to_om_h" => "",		
			},
			"out" => { 
				"data_from_om_e" => "",
				"data_from_om_s" => "",
				"data_from_om_w" => "",
				"data_from_om_n" => "",
				"data_from_om_h" => "",
				
				"valid_from_om_e" => "",
				"valid_from_om_s" => "",
				"valid_from_om_w" => "",
				"valid_from_om_n" => "",
				"valid_from_om_h" => "",
				
				"credits_from_queue_e" => "",
				"credits_from_queue_s" => "",
				"credits_from_queue_w" => "",
				"credits_from_queue_n" => "",
				"credits_from_queue_h" => "",
			},		
		}	
	}
}

# this hash has channels between routers
my %channels = ();

# this hash keepts track of all the wires
my %wires = (
	"global" => { "CLK" => 1, "RESET" => 1, },
	"data" => {},	
);

my %primary_io = ();

# perform wiring
if($args{cd} == 0) {	# if we don't have channels
	foreach my $router( keys %routers ) {
		my $r = $routers{$router};
		my ($data_wire,$valid_wire,$credit_wire,$this_router,$that_router) = ("","","","","");
		
		$this_router = $r->{id}{x} . "_" . $r->{id}{y};
		
		### Hook up EAST ports	
		if($r->{id}{x} == $args{x}) { # east-side router, no inputs coming from west, no outputs going east
			$r->{in}{"data_to_queue_e"} 	= "`EDGE_DATA_INPUT";
			$r->{in}{"valid_to_queue_e"} 	= "`EDGE_SIGNAL_INPUT";
			$r->{in}{"credits_to_om_e"} 	= "`EDGE_SIGNAL_INPUT";
			$r->{out}{"credits_from_queue_e"}	= "`EDGE_DUMMY_OUT";
			$r->{out}{"data_from_om_e"}	= "`EDGE_DUMMY_OUT";
			$r->{out}{"valid_from_om_e"}	= "`EDGE_DUMMY_OUT";		
		} else {
			$that_router = $r->{id}{x} + 1 . "_" . $r->{id}{y};
			# data wire out
			$data_wire = "data_from_${this_router}_to_$that_router";
			$r->{out}{"data_from_om_e"} = $data_wire;
			$wires{data}{$data_wire}++;		
			# data wire in
			$data_wire = "data_from_${that_router}_to_$this_router";
			$r->{in}{"data_to_queue_e"} = $data_wire;
			$wires{data}{$data_wire}++;
			
			# valid wire out
			$valid_wire = "valid_from_${this_router}_to_$that_router";
			$r->{out}{"valid_from_om_e"} = $valid_wire;
			$wires{valid}{$valid_wire}++;
			# valid wire in
			$valid_wire = "valid_from_${that_router}_to_$this_router";
			$r->{in}{"valid_to_queue_e"} = $valid_wire;
			$wires{valid}{$valid_wire}++;
			
			# credit wire out
			$credit_wire = "credit_from_${this_router}_to_$that_router";
			$r->{out}{"credits_from_queue_e"} = $credit_wire;
			$wires{credit}{$credit_wire}++;
			# credit wire in
			$credit_wire = "credit_from_${that_router}_to_$this_router";
			$r->{in}{"credits_to_om_e"} = $credit_wire;
			$wires{credit}{$credit_wire}++;		
		}
		
		### Hook up WEST ports	
		if($r->{id}{x} == 0) { # east-side router, no inputs coming from west, no outputs going east
			$r->{in}{"data_to_queue_w"} 	= "`EDGE_DATA_INPUT";
			$r->{in}{"valid_to_queue_w"} 	= "`EDGE_SIGNAL_INPUT";
			$r->{in}{"credits_to_om_w"} 	= "`EDGE_SIGNAL_INPUT";
			$r->{out}{"credits_from_queue_w"}	= "`EDGE_DUMMY_OUT";
			$r->{out}{"data_from_om_w"}	= "`EDGE_DUMMY_OUT";
			$r->{out}{"valid_from_om_w"}	= "`EDGE_DUMMY_OUT";		
		} else {
			$that_router = $r->{id}{x} - 1 . "_" . $r->{id}{y};
			# data wire out
			$data_wire = "data_from_${this_router}_to_$that_router";
			$r->{out}{"data_from_om_w"} = $data_wire;
			$wires{data}{$data_wire}++;		
			# data wire in
			$data_wire = "data_from_${that_router}_to_$this_router";
			$r->{in}{"data_to_queue_w"} = $data_wire;
			$wires{data}{$data_wire}++;
			
			# valid wire out
			$valid_wire = "valid_from_${this_router}_to_$that_router";
			$r->{out}{"valid_from_om_w"} = $valid_wire;
			$wires{valid}{$valid_wire}++;
			# valid wire in
			$valid_wire = "valid_from_${that_router}_to_$this_router";
			$r->{in}{"valid_to_queue_w"} = $valid_wire;
			$wires{valid}{$valid_wire}++;
			
			# credit wire out
			$credit_wire = "credit_from_${this_router}_to_$that_router";
			$r->{out}{"credits_from_queue_w"} = $credit_wire;
			$wires{credit}{$credit_wire}++;
			# credit wire in
			$credit_wire = "credit_from_${that_router}_to_$this_router";
			$r->{in}{"credits_to_om_w"} = $credit_wire;
			$wires{credit}{$credit_wire}++;		
		}
		
		### Hook up NORTH ports	
		if($r->{id}{y} == 0) { # east-side router, no inputs coming from west, no outputs going east
			$r->{in}{"data_to_queue_n"} 	= "`EDGE_DATA_INPUT";
			$r->{in}{"valid_to_queue_n"} 	= "`EDGE_SIGNAL_INPUT";
			$r->{in}{"credits_to_om_n"} 	= "`EDGE_SIGNAL_INPUT";
			$r->{out}{"credits_from_queue_n"}	= "`EDGE_DUMMY_OUT";
			$r->{out}{"data_from_om_n"}	= "`EDGE_DUMMY_OUT";
			$r->{out}{"valid_from_om_n"}	= "`EDGE_DUMMY_OUT";		
		} else {
			$that_router = $r->{id}{x} . "_";
			$that_router .=  $r->{id}{y} - 1;
			# data wire out
			$data_wire = "data_from_${this_router}_to_$that_router";
			$r->{out}{"data_from_om_n"} = $data_wire;
			$wires{data}{$data_wire}++;		
			# data wire in
			$data_wire = "data_from_${that_router}_to_$this_router";
			$r->{in}{"data_to_queue_n"} = $data_wire;
			$wires{data}{$data_wire}++;
			
			# valid wire out
			$valid_wire = "valid_from_${this_router}_to_$that_router";
			$r->{out}{"valid_from_om_n"} = $valid_wire;
			$wires{valid}{$valid_wire}++;
			# valid wire in
			$valid_wire = "valid_from_${that_router}_to_$this_router";
			$r->{in}{"valid_to_queue_n"} = $valid_wire;
			$wires{valid}{$valid_wire}++;
			
			# credit wire out
			$credit_wire = "credit_from_${this_router}_to_$that_router";
			$r->{out}{"credits_from_queue_n"} = $credit_wire;
			$wires{credit}{$credit_wire}++;
			# credit wire in
			$credit_wire = "credit_from_${that_router}_to_$this_router";
			$r->{in}{"credits_to_om_n"} = $credit_wire;
			$wires{credit}{$credit_wire}++;		
		}
		
		### Hook up SOUTH ports	
		if($r->{id}{y} == $args{y}) { # east-side router, no inputs coming from west, no outputs going east
			$r->{in}{"data_to_queue_s"} 	= "`EDGE_DATA_INPUT";
			$r->{in}{"valid_to_queue_s"} 	= "`EDGE_SIGNAL_INPUT";
			$r->{in}{"credits_to_om_s"} 	= "`EDGE_SIGNAL_INPUT";
			$r->{out}{"credits_from_queue_s"}	= "`EDGE_DUMMY_OUT";
			$r->{out}{"data_from_om_s"}	= "`EDGE_DUMMY_OUT";
			$r->{out}{"valid_from_om_s"}	= "`EDGE_DUMMY_OUT";		
		} else {
			$that_router = $r->{id}{x} . "_";
			$that_router .= $r->{id}{y} + 1;
			# data wire out
			$data_wire = "data_from_${this_router}_to_$that_router";
			$r->{out}{"data_from_om_s"} = $data_wire;
			$wires{data}{$data_wire}++;		
			# data wire in
			$data_wire = "data_from_${that_router}_to_$this_router";
			$r->{in}{"data_to_queue_s"} = $data_wire;
			$wires{data}{$data_wire}++;
			
			# valid wire out
			$valid_wire = "valid_from_${this_router}_to_$that_router";
			$r->{out}{"valid_from_om_s"} = $valid_wire;
			$wires{valid}{$valid_wire}++;
			# valid wire in
			$valid_wire = "valid_from_${that_router}_to_$this_router";
			$r->{in}{"valid_to_queue_s"} = $valid_wire;
			$wires{valid}{$valid_wire}++;
			
			# credit wire out
			$credit_wire = "credit_from_${this_router}_to_$that_router";
			$r->{out}{"credits_from_queue_s"} = $credit_wire;
			$wires{credit}{$credit_wire}++;
			# credit wire in
			$credit_wire = "credit_from_${that_router}_to_$this_router";
			$r->{in}{"credits_to_om_s"} = $credit_wire;
			$wires{credit}{$credit_wire}++;		
		}
		
		### Hook up HERE ports	
			$that_router = "here";
			# data wire out
			$data_wire = "data_from_${this_router}_to_$that_router";
			$r->{out}{"data_from_om_h"} = $data_wire;
			$wires{data}{$data_wire}++;
			$primary_io{data}{out}{"data_out_$this_router"} = $data_wire;
			# data wire in
			$data_wire = "data_from_${that_router}_to_$this_router";
			$r->{in}{"data_to_queue_h"} = $data_wire;
			$wires{data}{$data_wire}++;
			$primary_io{data}{in}{"data_in_$this_router"} = $data_wire;
			
			# valid wire out
			$valid_wire = "valid_from_${this_router}_to_$that_router";
			$r->{out}{"valid_from_om_h"} = $valid_wire;
			$wires{valid}{$valid_wire}++;
			$primary_io{valid}{out}{"valid_out_$this_router"} = $valid_wire;
			# valid wire in
			$valid_wire = "valid_from_${that_router}_to_$this_router";
			$r->{in}{"valid_to_queue_h"} = $valid_wire;
			$wires{valid}{$valid_wire}++;
			$primary_io{valid}{in}{"valid_in_$this_router"} = $valid_wire;
			
			# credit wire out
			$credit_wire = "credit_from_${this_router}_to_$that_router";
			$r->{out}{"credits_from_queue_h"} = $credit_wire;
			$wires{credit}{$credit_wire}++;
			$primary_io{credit}{out}{"credit_out_$this_router"} = $credit_wire;
			# credit wire in
			$credit_wire = "credit_from_${that_router}_to_$this_router";
			$r->{in}{"credits_to_om_h"} = $credit_wire;
			$wires{credit}{$credit_wire}++;
			$primary_io{credit}{in}{"credit_in_$this_router"} = $credit_wire;		
	}
}


# attempt to open file
if(-e $args{o} and not $args{ovr}) {
	die "output file $args{o} already exists. Run with '-ovr' to overwrite.\n";
}
my $fh;
open($fh, ">", $args{o}) or die "Cannot open $args{o} for write: $!\n";


# Print Output

# print includes and defines
print $fh <<HEADER1;
`include \"defines.d\"	// global definitions
`define EDGE_DUMMY_OUT	// defined to nothing to simulate a missing hook
`define EDGE_DATA_INPUT {`DATA_WIDTH{1'b0}}	// push in zeros for data at an edge
`define EDGE_SIGNAL_INPUT 1'b0	// push in zeros for signals (i.e., valid, credits) at an edge

$comment
// Autogenerated by $0

module network (

HEADER1

foreach my $io ("in", "out") {
	print $fh "\t// Primary ${io}puts\n\t";
	foreach my $type ("data", "valid", "credit") {
		foreach my $sig (sort {$a cmp $b} keys %{$primary_io{$type}{$io}}) {
			print $fh " $sig,";
		}
		print $fh "\n\t";
	}
	print $fh "\n";	
}


print $fh "\t// global network inputs\n\tCLK, RESET\n";
print $fh ");\n\n\t// Global network inputs\n\tinput CLK, RESET;\n\n";


my ($total, $count) = (0,0);
# declare primary IO;
foreach my $io ("in", "out") {
	print $fh "\t// Primary ${io}puts\n";
	foreach my $type ("data", "valid", "credit") {
		print $fh "\t${io}put wire";
		print $fh " [`DATA_WIDTH-1:0]" if($type eq "data");
		$total = scalar keys %{$primary_io{$type}{$io}};
		$count = 0;
		foreach my $sig (sort {$a cmp $b} keys %{$primary_io{$type}{$io}}) {
			print $fh " $sig";
			$count++;
			print $fh ($count == $total) ? ";\n" : ",";
		}
	}
	print $fh "\n";	
}





print $fh "\n\n";



# print wires


$total = scalar keys %{$wires{data}};
$count = 0;
print $fh "\t// Data Busses ($total total)\n\twire [`DATA_WIDTH-1:0]";
foreach my $wire (sort {$a cmp $b} keys %{$wires{data}} ) {
	print $fh " $wire";
	$count++;
	print $fh ($count == $total) ? ";\n" : ",";
}

$total = scalar keys %{$wires{valid}};
$count = 0;
print $fh "\t// Valid wires ($total total)\n\twire ";
foreach my $wire (sort {$a cmp $b} keys %{$wires{valid}} ) {
	print $fh " $wire";
	$count++;
	print $fh ($count == $total) ? ";\n" : ",";
}

$total = scalar keys %{$wires{credit}};
$count = 0;
print $fh "\t// Credit wires ($total total)\n\twire ";
foreach my $wire (sort {$a cmp $b} keys %{$wires{credit}} ) {
	print $fh " $wire";
	$count++;
	print $fh ($count == $total) ? ";\n" : ",";
}


print $fh "\n\n\t// Routers\n";

foreach my $router ( sort {$a cmp $b} keys %routers ) {
	print $fh "\trouter #(.router_id_x($routers{$router}{id}{x}), .router_id_y($routers{$router}{id}{y})) $router(\n\t\t// Inputs\n";
	# inputs
	$total = scalar keys %{$routers{$router}{in}};
	$count = 0;		
	foreach my $input ( sort {$a cmp $b} keys %{$routers{$router}{in}} ) {
		next if ($routers{$router}{in}{$input} eq "CLK" or $routers{$router}{in}{$input} eq "RESET");
		print $fh "\t\t.$input($routers{$router}{in}{$input}),\n";
		$count++;
		#print $fh ($count == $total) ? "\n" : ",\n";
	}	
	# outputs
	$total = scalar keys %{$routers{$router}{out}};
	$count = 0;
	print $fh "\t\t// Outputs\n";
		foreach my $output ( sort {$a cmp $b} keys %{$routers{$router}{out}} ) {
		print $fh "\t\t.$output($routers{$router}{out}{$output}),\n";
		$count++;
		#print $fh ($count == $total) ? "\n" : ",\n";
	}
	print $fh "\t\t// Global inputs\n\t\t.clock(CLK), .reset(RESET)\n";
	
	
	print $fh "\t); // router $router\n\n";
}


# assign primary IO;
foreach my $io ("in", "out") {
	print $fh "\t// Primary ${io}puts\n";
	foreach my $type ("data", "valid", "credit") {
		print $fh "\t// $type ${io}put assignments\n";	
		$total = scalar keys %{$primary_io{$type}{$io}};
		$count = 0;
		foreach my $sig (sort {$a cmp $b} keys %{$primary_io{$type}{$io}}) {
			
			print $fh ($io eq "in") ? "\tassign $primary_io{$type}{$io}{$sig} = $sig;\n" : "\tassign $sig = $primary_io{$type}{$io}{$sig};\n"
		}
	}
	print $fh "\n";	
}


print $fh "\n\nendmodule // NETWORK\n";


print STDOUT "Output file: $args{o}\n";


sub printHash {
	my $h = shift;
	my $tabs = ($_ = shift) ? $_ : 0;
	
	foreach my $key (sort keys %{ $h } ){
		for(0 .. $tabs - 1) { print "... "; }
		print "$key => ";
		if( ref($h->{$key}) eq "HASH" ){
			print "\n";
			&printHash($h->{$key}, $tabs+1);
		} else {
			print "'$h->{$key}'\n";
		}	
	}	
}
